/*
PearReview - The peer review assistant.
Copyright (C) 2009  Dimitrij Pankratz, Anton Musichin
http://www.pearreview.com, info@pearreview.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package com.pearreview.moderatorplugin;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Formatter;
import java.util.Random;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.GroupLayout;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.LayoutStyle;
import javax.swing.ToolTipManager;
import javax.swing.TransferHandler;
import javax.swing.border.EmptyBorder;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.plaf.basic.BasicSplitPaneDivider;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import com.pearreview.ui.BottomButtonPanel;
import com.pearreview.ui.HeadingLabel;
import com.pearreview.ui.PearPanel;
import com.pearreview.ui.slidebox.AttendeeSlideBoxPanel;
import com.pearreview.ui.slidebox.SlideBoxChangeEvent;
import com.pearreview.ui.slidebox.SlideBoxChangeListener;
import com.pearreview.ui.tree.*;
import com.pearreview.application.Messages;
import com.pearreview.util.review.Aspect;
import com.pearreview.util.review.Aspects;
import com.pearreview.util.review.Attendee;
import com.pearreview.util.review.AttendeeRole;
import com.pearreview.util.review.IdArrayList;
import com.pearreview.util.review.IdLinkedList;
import com.pearreview.util.review.IdList;

public class DistributerPanel extends PearPanel implements ModeratorPanel,
		SlideBoxChangeListener, AspectsPanel.SimpleChangeListener {
	private static final long serialVersionUID = 7538504195981419698L;
	
	private JTree aspectsTree;
	private JTree reviewersTree;
	
	private JPopupMenu reviewersPopupMenu;
	
	private CatalogTreeModel reviewersModel;
	
	private JButton distributeButton;
	private JComboBox distributeComboBox;
	
	private Action deleteAction = new DeleteAction();
	private Action addAction = new AddAction();
	private Action distributeAction = new DistributeAction();
	
	private TreeSelectionListener reviewersSelectionListener = 
			new ReviewersTreeSelectionListener();
	private TreeSelectionListener aspectsTreeSelectionListener = 
			new AspectsTreeSelectionListener();
	
	private JTree dragTree = null;
	
	private AttendeeSlideBoxPanel attendeeList;
	
	private BottomButtonPanel bottom;
	
	public DistributerPanel() {
		initComponents();
		
		reviewersModel = new CatalogTreeModel();
		reviewersModel
				.createEmptyAspectsRoot(getLocalizedString("reviewers"));
		reviewersTree.setModel(reviewersModel);

		/* selection listener */
		reviewersTree.getSelectionModel()
				.addTreeSelectionListener(reviewersSelectionListener);
		
		aspectsTree.getSelectionModel()
				.addTreeSelectionListener(aspectsTreeSelectionListener);
		
		/* menu listener */
		reviewersTree.addMouseListener(new ReviewersPopupMouseAdapter());
		

		/* DnD */
		MouseListener ml = new MouseAdapter() {
			public void mousePressed(MouseEvent e) {
				if(e.getSource() instanceof JTree) {
					dragTree = (JTree)e.getSource();
				}
			}
		};
		
		reviewersTree.addMouseListener(ml);
		aspectsTree.addMouseListener(ml);
		
		reviewersTree.setDragEnabled(true);
		aspectsTree.setDragEnabled(true);
		
		aspectsTree.setDropTarget(new ExpandPathDropTarget(aspectsTree, 
				TransferHandler.NONE, null) {
			private static final long serialVersionUID = 8044040772100996948L;

			@Override
			public void dragOver(DropTargetDragEvent dtde) {
				super.dragOver(dtde);
				dtde.acceptDrag(TransferHandler.NONE);
			}
		});
		
		DropTargetAdapter dta = new TreeDropTargetAdapter();
		
		reviewersTree.setDropTarget(new ExpandPathDropTarget(aspectsTree, 
				TransferHandler.COPY_OR_MOVE, dta){
			private static final long serialVersionUID = 3137854954016945031L;
			
			@Override
			public void dragOver(DropTargetDragEvent dtde) {
				super.dragOver(dtde);
				
				Point p = dtde.getLocation();
				TreePath targetPath = 
						reviewersTree.getClosestPathForLocation(p.x, p.y);
				
				if(dragTree == aspectsTree) {
					if(!canMoveFromAspectsTree(targetPath))
						dtde.acceptDrag(TransferHandler.NONE);
				} else if(dragTree == reviewersTree) {
					dtde.acceptDrag(TransferHandler.NONE);
				} else {
					dtde.acceptDrag(TransferHandler.NONE);
				}
			}	
		});
	}
	
	private void initComponents() {
		setLayout(new BorderLayout());
		
		Component headingLabel = new HeadingLabel(
				Messages.getString("PearReview.Moderator.tabs.distribute"));
		add(headingLabel, BorderLayout.NORTH);

		reviewersPopupMenu = new JPopupMenu();
		
		reviewersTree = new JTree();
		KeyStroke deleteKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 
				0);
		reviewersTree.getInputMap().put(deleteKeyStroke, "removeNodes");
		reviewersTree.getActionMap().put("removeNodes", deleteAction);
		reviewersTree.add(reviewersPopupMenu);
		reviewersTree.setRowHeight(0);
		reviewersTree.setBorder(new EmptyBorder(10, 10, 10, 10));
		reviewersTree.setCellRenderer(new CatalogTreeCellRenderer());
		ToolTipManager.sharedInstance().registerComponent(reviewersTree);
		JScrollPane aspectsScrollPane = new JScrollPane(reviewersTree);
		aspectsScrollPane.setBorder(new EmptyBorder(0, 0, 0, 0));
		
		aspectsTree = new JTree();
		aspectsTree.setRowHeight(0);
		aspectsTree.setBorder(new EmptyBorder(10, 10, 10, 10));
		aspectsTree.setCellRenderer(new CatalogTreeCellRenderer());
		ToolTipManager.sharedInstance().registerComponent(aspectsTree);
		JScrollPane catalogsScrollPane = new JScrollPane(aspectsTree);
		catalogsScrollPane.setBorder(new EmptyBorder(0, 0, 0, 0));
		
		
		JPanel formPanel = new JPanel();
		formPanel.setOpaque(false);
		
		
		JSplitPane splitPane = new JSplitPane();
		splitPane.setResizeWeight(0.5);
		splitPane.setContinuousLayout(true);
		splitPane.setBackground(new Color(0, true));
		BasicSplitPaneDivider dividerContainer = 
				(BasicSplitPaneDivider) splitPane.getComponent(2);
		splitPane.setRightComponent(aspectsScrollPane);
		splitPane.setLeftComponent(catalogsScrollPane);
		
		JPanel buttonsPanel = new JPanel();
		JButton addButton = new JButton();
		JButton subButton = new JButton();
		addButton.setAction(addAction);
		subButton.setAction(deleteAction);
		addButton.setText(null);
		subButton.setText(null);
		addButton.setToolTipText(getLocalizedString("add"));
		subButton.setToolTipText(getLocalizedString("remove"));
		addButton.setEnabled(false);
		subButton.setEnabled(false);
		addButton.setOpaque(false);
		subButton.setOpaque(false);
		buttonsPanel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
		addButton.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
		subButton.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
		buttonsPanel.setBackground(new Color(0, true));
		buttonsPanel.setOpaque(false);
		GroupLayout btnLayout = new GroupLayout(buttonsPanel);
		buttonsPanel.setLayout(btnLayout);
		btnLayout.setHorizontalGroup(
				btnLayout.createParallelGroup()
				.addGroup(btnLayout.createSequentialGroup()
					.addGroup(btnLayout.createParallelGroup()
						.addComponent(addButton)
						.addComponent(subButton)))
		);
		btnLayout.setVerticalGroup(
				btnLayout.createParallelGroup()
				.addGroup(btnLayout.createSequentialGroup()
					.addComponent(addButton)
					.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
					.addComponent(subButton))
		);
		
		dividerContainer.setBackground(new Color(0, true));
		
		GridBagLayout gbl = new GridBagLayout();
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.anchor = GridBagConstraints.CENTER;
		dividerContainer.setLayout(gbl);
		gbl.setConstraints(buttonsPanel, gbc);
		dividerContainer.add(buttonsPanel);
		int size = buttonsPanel.getPreferredSize().width;
		size += 6; // gaps
		dividerContainer.setDividerSize(size);
		
		
		distributeButton = new JButton();
		distributeButton.setAction(distributeAction);
		distributeComboBox = new JComboBox();
		distributeComboBox.setFont(new Font("Tahoma",Font.PLAIN, 12));
		if (System.getProperty("os.name").equals("Mac OS X")) {
			distributeComboBox.setBorder(BorderFactory.createEmptyBorder(
					0, 0, 0, 0));
		} else {
			distributeComboBox.setBorder(BorderFactory.createMatteBorder(
							1, 1, 1, 1, new Color(0x828790)));
		}
		JLabel distributeLabel = 
				new JLabel(getLocalizedString("distribute.aspects"));
		JPanel distributePanel = new JPanel();
		distributePanel.setOpaque(false);
		
		GroupLayout distributelayout = new GroupLayout(distributePanel);
		distributePanel.setLayout(distributelayout);
		distributelayout.setHorizontalGroup(
			distributelayout.createParallelGroup()
				.addGroup(distributelayout.createSequentialGroup()
					.addContainerGap()
					.addComponent(distributeLabel)
					.addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
					.addComponent(distributeComboBox, 
							200, 
							GroupLayout.PREFERRED_SIZE, 
							GroupLayout.PREFERRED_SIZE)
					.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
					.addComponent(distributeButton))
		);
		distributelayout.setVerticalGroup(
			distributelayout.createParallelGroup()
				.addGroup(distributelayout.createSequentialGroup()
					.addContainerGap()
					.addGroup(distributelayout.createParallelGroup(
								GroupLayout.Alignment.BASELINE)
						.addComponent(distributeLabel)
						.addComponent(distributeComboBox, 
								GroupLayout.PREFERRED_SIZE, 
								GroupLayout.DEFAULT_SIZE, 
								GroupLayout.PREFERRED_SIZE)
						.addComponent(distributeButton)))
		);
		
		GroupLayout layout = new GroupLayout(formPanel);
		formPanel.setLayout(layout);
		layout.setHorizontalGroup(
			layout.createParallelGroup()
				.addGroup(layout.createSequentialGroup()
					.addContainerGap()
					.addGroup(layout.createParallelGroup()
						.addComponent(splitPane, 
								GroupLayout.DEFAULT_SIZE, 
								0, 
								Short.MAX_VALUE)
						.addComponent(distributePanel))
					.addContainerGap())
		);
		layout.setVerticalGroup(
			layout.createParallelGroup()
				.addGroup(layout.createSequentialGroup()
					.addContainerGap()
					.addComponent(splitPane, 
							GroupLayout.DEFAULT_SIZE, 
							0, 
							Short.MAX_VALUE)
					.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
					.addComponent(distributePanel)
					.addContainerGap())
		);
		
		add(formPanel, BorderLayout.CENTER);
		
		bottom = new BottomButtonPanel();
		add(bottom, BorderLayout.SOUTH);
	}

	/**
	 * Sets model for aspects tree
	 * @param aspectsModel aspects model to set
	 */
	public void setAspectsModel(CatalogTreeModel aspectsModel) {
		aspectsTree.setModel(aspectsModel);
		aspectsTree.setSelectionPath(
				new TreePath(aspectsTree.getModel().getRoot()));
	}
	
	/**
	 * Returns aspects tree model
	 * @return aspects tree model
	 */
	public CatalogTreeModel getAspectsModel() {
		return (CatalogTreeModel)aspectsTree.getModel();
	}
	
	@Override
	public boolean isValidForm() {
		return true;
	}
	
	/**
	 * Applies attendees aspects
	 */
	public void applyAttendeeAspects() {
		DefaultTreeNode dtnRoot = reviewersModel.getRoot();
		int childCount = dtnRoot.getChildCount();
		for(int i = 0 ; i < childCount ; i++) {
			DefaultTreeNode dtnChild = dtnRoot.getChildAt(i);
			
			if(!(dtnChild instanceof AspectsTreeNode))
				continue;
			
			if(!(dtnChild.getCustomObject() instanceof Attendee))
				continue;
			
			AspectsTreeNode atn = (AspectsTreeNode)dtnChild;
			Attendee attendee = (Attendee)dtnChild.getCustomObject();
			
			IdList<Aspect> aspects = 
					reviewersModel.getAspectsOfAspectsNode(atn);
			attendee.setAspects(aspects);
		}
		
		/* remove all empty aspects list */
		int attendeeCount = attendeeList.getListSize();
		for(int i = 0 ; i < attendeeCount ; i++) {
			Attendee attendee = attendeeList.getListElement(i);
			if(attendee.getAspects()!= null 
					&& attendee.getAspects().size() <= 0) {
				attendee.setAspects(null);
			}
		}
	}
	
	/**
	 * Checks if path can be removed
	 * @param path path to check
	 * @return <code>true</code> if path can be removed, otherwise
	 * <code>false</code>
	 */
	private boolean canRemove(TreePath path) {
		return true;
	}
	
	/**
	 * Checks if path can be removed
	 * @param path path to check
	 * @return <code>true</code> if path can be removed, otherwise
	 * <code>false</code>
	 */
	private boolean canRemove(TreePath[] paths) {
		if(paths == null || paths.length == 0)
			return false;
		
		paths = CatalogTreeModel.removeLowerNodes(paths);
			
		if(paths == null || paths.length == 0)
			return false;
		
		for(int i = 0 ; i < paths.length ; i++) {
			if(!canRemove(paths[i]))
				return false;
		}
		
		return true;
	}
	
	/**
	 * Moves selected elements in aspects tree to reviewers tree
	 * @param targetPath
	 */
	private void moveFromAspectsTree(TreePath[] targetPaths) {
		if(targetPaths == null)
			return;
		for(int i = 0 ; i < targetPaths.length ; i++) {
			moveFromAspectsTree(targetPaths[i]);
		}
	}
	
	/**
	 * Moves selected elements in aspects tree to reviewers tree
	 * @param targetPath
	 */
	private void moveFromAspectsTree(TreePath targetPath) {
		if(targetPath == null)
			targetPath = new TreePath(reviewersModel.getRoot());
		
		Object node = targetPath.getLastPathComponent();
		
		if(node instanceof AspectsRootTreeNode) {
			AspectsRootTreeNode artn = (AspectsRootTreeNode)node;
			for(int i = 0 ; i < artn.getChildCount() ; i++) {
				TreePath childPath = targetPath
						.pathByAddingChild(artn.getChildAt(i));
				
				moveFromAspectsTreeToReviewer(childPath);
			}
		} else {
			moveFromAspectsTreeToReviewer(targetPath);
		}
	}
	
	private void moveFromAspectsTreeToReviewer(TreePath target, 
			TreePath[] source) {
		if(source == null || source.length == 0)
			return;
		
		for(int i = 0 ; i < source.length ; i++) {
			TreePath pathToAdd = source[i];
			int level = CatalogTreeModel.getNodeLevel(pathToAdd);
			
			switch(level) {
			/* aspect */
			case 4:
				moveAspectFromAspectsTreeToReviewer(pathToAdd, target);
				break;
				
			/* category */
			case 3:
				moveCategoryFromAspectsTreeToReviewer(pathToAdd, target);
				break;
				
			/* aspects list */
			case 2:
				moveAspectsListFromAspectsTreeToReviewer(pathToAdd, target);
				break;
			}
		}
	}
	
	private void moveAllAspectsToReviewers() {
		
		TreeNode root = reviewersModel.getRoot();
		TreePath rootPath = new TreePath(root);
		
		int childCount = root.getChildCount();
		
		TreePath[] source = {new TreePath(getAspectsModel().getRoot())};
		
		for(int i = 0 ; i < childCount ; i++) {
			TreePath target = rootPath.pathByAddingChild(root.getChildAt(i));
			moveFromAspectsTreeToReviewer(target, source);
		}
	}
	
	/**
	 * Moves selected elements in aspects tree to reviewers tree
	 * @param targetPath
	 */
	private void moveFromAspectsTreeToReviewer(TreePath target) {
		TreePath[] source = CatalogTreeModel
				.removeLowerNodes(aspectsTree.getSelectionPaths());
		
		if(source == null || source.length == 0)
			return;
		
		moveFromAspectsTreeToReviewer(target, source);
	}
	
	private TreePath moveAspectFromAspectsTreeToReviewer(TreePath pathToAdd, 
			TreePath targetPath) {
		Object targetNode = targetPath.getLastPathComponent();
		int insertIndex = -1;
		if(targetNode instanceof AspectTreeNode) {
			AspectTreeNode atn =(AspectTreeNode) targetNode;
			insertIndex = atn.getParent().getIndex(atn);
		}
		
		Object nodeToAdd = pathToAdd.getLastPathComponent();
		
		if(!(nodeToAdd instanceof AspectTreeNode))
			throw new IllegalArgumentException();
		
		
		AspectTreeNode aspectNodeToAdd = (AspectTreeNode)nodeToAdd;
		
		Aspect insertAspect = aspectNodeToAdd.getAspect();
		
		Object parent = aspectNodeToAdd.getParent();
		
		TreePath insertPath;
		
		if(parent instanceof CategoryTreeNode) {
			String category = ((CategoryTreeNode)parent).getName();
			insertAspect.setCategory(category);
			insertPath = getReviewerCategory(targetPath, category, true);
		} else {
			insertAspect.setCategory("");
			insertPath = new TreePath(reviewersModel.getRoot());
			insertPath = 
				insertPath.pathByAddingChild(targetPath.getPathComponent(1));
		}
		
		/* check if already inserted */
		Object insertNode = insertPath.getLastPathComponent();
		if(insertNode != null && insertNode instanceof TreeNode) {
			TreeNode parentNode = (TreeNode)insertNode;
			int count = parentNode.getChildCount();
			
			for(int i = 0 ; i < count ; i++) {
				if(!(parentNode.getChildAt(i) instanceof AspectTreeNode))
					continue;
				AspectTreeNode atn = (AspectTreeNode)parentNode.getChildAt(i);
				
				if(atn.getAspect() == insertAspect)
					return insertPath.pathByAddingChild(atn);
			}
		}
		
		return reviewersModel.insertAspectNode(insertPath, 
				insertAspect, 
				insertIndex);
		
	}
	
	private TreePath moveCategoryFromAspectsTreeToReviewer(
			TreePath pathToAdd, TreePath targetPath) {
		boolean expanded = aspectsTree.isExpanded(pathToAdd);
		
		Object node = pathToAdd.getLastPathComponent();
		
		if(!(node instanceof CategoryTreeNode))
			throw new IllegalArgumentException();
		
		CategoryTreeNode ctn = (CategoryTreeNode)node;
		
		String category = ctn.getName();
		
		TreePath aspectsCategoryPath = 
				getReviewerCategory(targetPath, category ,true);
		
		/* get children */
		Enumeration<?> e = ctn.children();
		while(e.hasMoreElements()) {
			TreePath childPath = pathToAdd.pathByAddingChild(e.nextElement());
			moveAspectFromAspectsTreeToReviewer(childPath, targetPath);
		}
		
		if(expanded)
			aspectsTree.expandPath(aspectsCategoryPath);
		
		return aspectsCategoryPath;
	}
	
	private TreePath[] moveAspectsListFromAspectsTreeToReviewer(
			TreePath pathToAdd, TreePath targetPath) {
		Object node = pathToAdd.getLastPathComponent();
		
		if(!(node instanceof AspectsTreeNode))
			throw new IllegalArgumentException();
		
		AspectsTreeNode atn = (AspectsTreeNode)node;
		
		int cildCount = atn.getChildCount();
		
		TreePath[] addedPath = new TreePath[cildCount];
		
		for(int i = 0 ; i < cildCount ; i++) {
			TreePath cPath = pathToAdd.pathByAddingChild(atn.getChildAt(i));
			
			Object cNode = cPath.getLastPathComponent();
			
			if(cNode instanceof CategoryTreeNode) {
				addedPath[i] = moveCategoryFromAspectsTreeToReviewer(cPath, 
							targetPath);
			} else if(cNode instanceof AspectTreeNode) {
				addedPath[i] = moveAspectFromAspectsTreeToReviewer(cPath, 
						targetPath);
			} else {
				throw new IllegalArgumentException();
			}
		}
		
		return addedPath;
	}
	
	/**
	 * Gets category path.
	 * @return category path
	 */
	private TreePath getReviewerCategory(TreePath targetPath, String category,
			boolean create) {
		AspectsTreeNode atn = (AspectsTreeNode)targetPath.getPathComponent(1);
		
		TreePath rootPath = new TreePath(reviewersModel.getRoot());
		
		TreePath path = rootPath.pathByAddingChild(atn);
		
		Enumeration<?> children = atn.children();
		while(children.hasMoreElements()) {
			TreeNode child = (TreeNode)children.nextElement();
			if(child instanceof CategoryTreeNode) {
				if(((CategoryTreeNode)child).getName()
						.equals(category)) {
					return path.pathByAddingChild(child);
				}
			}
		}
		
		if(!create)
			return null;
		
		/* get insert index */
		int insertIndex = -1;
		Object targetNode = targetPath.getLastPathComponent();
		if(targetNode instanceof CategoryTreeNode) {
			CategoryTreeNode ctn =(CategoryTreeNode) targetNode;
			insertIndex = ctn.getParent().getIndex(ctn);
		}
		
		return reviewersModel.insertCategoryNode(path, 
				category, 
				insertIndex);
	}
	
	/**
	 * Checks if selected items in aspects tree can be moved to 
	 * reviewers tree at target path
	 * @param targetPath
	 * @return
	 */
	private boolean canMoveFromAspectsTree(TreePath targetPath) {		
		Object node = null;
		if(targetPath != null) {
			node = targetPath.getLastPathComponent();
		}
		int lvl = CatalogTreeModel
				.getUpperNodeLevel(aspectsTree.getSelectionPaths());
		if(lvl < 2)
			return false;
		
		if(node == null)
			return false;
		
		return true;
	}
	
	/**
	 * Checks if selected items in aspects tree can be moved to 
	 * reviewers tree at target path
	 * @param targetPath
	 * @return
	 */
	private boolean canMoveFromAspectsTree(TreePath[] targetPaths) {		
		if(targetPaths == null || targetPaths.length == 0)
			return false;
		for(int i = 0 ; i < targetPaths.length ; i++) {
			if(!canMoveFromAspectsTree(targetPaths[i])) {
				return false;
			}
		}
		
		return true;
	}
	
	private void removeFromReviewersTree(TreePath path) {
		Object node = path.getLastPathComponent();
		
		if(node instanceof AspectTreeNode) {
			TreeNode parent = ((TreeNode)node).getParent();
			reviewersModel.removeNode(path);
			
			if(parent instanceof CategoryTreeNode) {
				if(parent.getChildCount() == 0) {
					reviewersModel.removeNode(path.getParentPath());
				}
			}
		} else if(node instanceof AspectsTreeNode) {
			/* remove all aspects/categories */
			reviewersModel.removeChildren(path);
		} else if(node instanceof AspectsRootTreeNode) {
			AspectsRootTreeNode artn = (AspectsRootTreeNode)node;
			int childCount = artn.getChildCount();
			for(int i = 0 ; i < childCount ; i++) {
				Object child = artn.getChildAt(i);
				reviewersModel.removeChildren(path.pathByAddingChild(child));
			}
		} else {
			reviewersModel.removeNode(path);
		}
	}
	
	private Icon getRessourceIcon(String name) {
		return new ImageIcon(getClass().getResource("images/" + name));
	}
	
	private static String getLocalizedString(String key) {
		return Messages.getString("PearReview.Moderator.distributer." + key);
	}
	
	private class DeleteAction extends AbstractAction {
		private static final long serialVersionUID = 5686085243839917076L;
		public DeleteAction() {
			setEnabled(false);
			
			Icon largeIcon = getRessourceIcon("edit-delete.png");
			Icon smallIcon = getRessourceIcon("edit-delete_16.png");
			
			putValue(Action.LARGE_ICON_KEY, largeIcon);
			putValue(Action.SMALL_ICON, smallIcon);
			
			putValue(Action.NAME, getLocalizedString("remove"));			
			
		}
		@Override
		public void actionPerformed(ActionEvent e) {
			final TreePath[] selectedPaths = reviewersTree.getSelectionPaths();
			
			TreePath[] deletePath = 
					CatalogTreeModel.removeLowerNodes(selectedPaths);
			
			if(deletePath == null || deletePath.length == 0)
				return;
			
			for(TreePath cPath : deletePath) {
				removeFromReviewersTree(cPath);
			}
		}
	}
	
	private class AddAction extends AbstractAction implements Serializable {
		private static final long serialVersionUID = -1240120114768257963L;
		public AddAction() {
			setEnabled(false);
			
			Icon largeIcon = getRessourceIcon("go-next.png");
			Icon smallIcon = getRessourceIcon("go-next_16.png");
			
			putValue(Action.LARGE_ICON_KEY, largeIcon);
			putValue(Action.SMALL_ICON, smallIcon);
			
			putValue(AbstractAction.NAME, getLocalizedString("add"));			
		}
		@Override
		public void actionPerformed(ActionEvent e) {
			moveFromAspectsTree(reviewersTree.getSelectionPaths());
		}
	}
	
	private class ReviewersTreeSelectionListener implements 
			TreeSelectionListener, Serializable {
		private static final long serialVersionUID = 1851572582847286099L;
		@Override
		public void valueChanged(TreeSelectionEvent e) {
			
			TreePath[] selectedPaths = reviewersTree.getSelectionPaths();
			
			deleteAction.setEnabled(canRemove(selectedPaths));			
			
			addAction.setEnabled(canMoveFromAspectsTree(selectedPaths));
		}
	}
	
	private class AspectsTreeSelectionListener implements 
			TreeSelectionListener, Serializable {
		private static final long serialVersionUID = 6822451296788625932L;
		@Override
		public void valueChanged(TreeSelectionEvent e) {
			TreePath[] selectedPaths = reviewersTree.getSelectionPaths();
			
			addAction.setEnabled(canMoveFromAspectsTree(selectedPaths));
		}
	}
	
	private class ReviewersPopupMouseAdapter extends MouseAdapter
			implements Serializable {
		private static final long serialVersionUID = 2340985036632808604L;
		
		private void popup(MouseEvent e) {
			reviewersPopupMenu.removeAll();
			int x = e.getX();
			int y = e.getY();
			TreePath clickedPath = reviewersTree.getPathForLocation(x, y);
			TreePath[] selectedPaths = reviewersTree.getSelectionPaths();
			
			if(clickedPath == null)
				return;
			
			if(selectedPaths != null 
					&& Arrays.asList(selectedPaths).contains(clickedPath) 
					&& selectedPaths.length > 1) {
					
			} else {
				reviewersTree.setSelectionPath(clickedPath);
				selectedPaths = new TreePath[]{clickedPath};
			}
			
			if(canRemove(selectedPaths)) {
				JMenuItem item = new JMenuItem();
				item.setAction(deleteAction);
				reviewersPopupMenu.add(item);
			}
			
			if(reviewersPopupMenu.getComponentCount() > 0)
				reviewersPopupMenu.show(reviewersTree, x, y);
		}

		@Override
		public void mouseReleased(MouseEvent e) {
			if (!e.isPopupTrigger())
				return;
			
			popup(e);
		}
		
		@Override
		public void mousePressed(MouseEvent e) {
			if (!e.isPopupTrigger())
				return;
			
			popup(e);
		}
	}
	
	private class TreeDropTargetAdapter extends DropTargetAdapter
			implements Serializable {
		private static final long serialVersionUID = 693272052029182660L;
		
		@Override
		public void drop(DropTargetDropEvent dtde) {
			Point p = dtde.getLocation();
			TreePath targetPath = 
					reviewersTree.getClosestPathForLocation(p.x, p.y);
			if(dragTree == reviewersTree) {
				/*
				TreePath[] sourcePaths = 
						reviewersTree.getSelectionPaths();
				
				// swap not supported
				if(sourcePaths != null && sourcePaths.length == 1 
						&& reviewersModel.canSwapNodes(targetPath, 
								sourcePaths[0])) {
					
					TreePath sourcePath = sourcePaths[0];
					
					// save expanded nodes
					boolean sourceExpanded = 
							reviewersTree.isExpanded(sourcePath);
					boolean targetExpanded = 
							reviewersTree.isExpanded(targetPath);
					
					// swaps 
					reviewersModel.swapNodes(targetPath, sourcePath);
					reviewersTree.setSelectionPath(sourcePath);
					
					// expand
					if(sourceExpanded)
						reviewersTree.expandPath(sourcePath);
					if(targetExpanded)
						reviewersTree.expandPath(targetPath);
					
					return;
				} */
				/* do nothing */
				
				dtde.rejectDrop();
				dtde.dropComplete(false);
				return;
				
			} else if(dragTree == aspectsTree) {
				if(canMoveFromAspectsTree(targetPath)) {
					moveFromAspectsTree(targetPath);
				}
			} else {
				dtde.rejectDrop();
				dtde.dropComplete(false);
			}
		}
	}
	
	/**
	 * Sets attendee slide box panel
	 * @param attendees
	 */
	public void setAttendeeSlideBoxPanel(AttendeeSlideBoxPanel attendees) {
		if(attendeeList != null) {
			attendeeList.removeSlideBoxChangeListener(this);
			//attendeeChangeListener.clearedPerformed(null);
		}
		attendeeList = attendees;
		attendeeList.addSlideBoxChangeListener(this);
		
		reviewersModel
				.createEmptyAspectsRoot(getLocalizedString("reviewers"));
		
		TreePath rootPath = new TreePath(reviewersModel.getRoot());
		
		int count = attendeeList.getListSize();
		
		for(int i = 0 ; i < count ; i++) {
			Attendee attendee = attendeeList.getListElement(i);
			if(!attendee.getRole().equals(AttendeeRole.Reviewer))
				continue;
			
			if(attendee.getAspects() == null)
				attendee.setAspects(new IdArrayList<Aspect>());
			
			TreePath addedPath = reviewersModel.addAspectsNode(rootPath, 
					attendee.getAspects(), 
					attendee.getName());
			
			DefaultTreeNode dtn = (DefaultTreeNode)addedPath
					.getLastPathComponent();
			
			dtn.setCustomObject(attendee);
			
			reviewersTree.expandPath(addedPath);
		}
		
		validateDistributer();
		
		reviewersTree.setSelectionPath(rootPath);
	}
	
	private class DistributeAction extends AbstractAction {
		private static final long serialVersionUID = 5686085243839917076L;
		public DistributeAction() {
			//setEnabled(false);
			
			//Icon largeIcon = getRessourceIcon("edit-delete.png");
			//Icon smallIcon = getRessourceIcon("edit-delete_16.png");
			
			//putValue(Action.LARGE_ICON_KEY, largeIcon);
			//putValue(Action.SMALL_ICON, smallIcon);
			
			putValue(Action.NAME, getLocalizedString("distribute"));
			
		}
		@Override
		public void actionPerformed(ActionEvent e) {
			int to = distributeComboBox.getSelectedIndex();
			
			int count = reviewersModel.getRoot().getChildCount();
			
			if(count <= 0 || getAspectsModel().getRoot() == null 
					||getAspectsModel().getRoot().getChildCount() <= 0) {
				return;
			}
			
			if(count == to || to == 0) {
				moveAllAspectsToReviewers();
				return;
			}
			
			// OTHER DISTRIBUTER?!
			
			IdList<Attendee> reviewers = new IdLinkedList<Attendee>();
			int aCount = attendeeList.getListSize();
			
			for(int i = 0 ; i < aCount ; i++) {
				Attendee attendee = attendeeList.getListElement(i);
				if(attendee.getRole().equals(AttendeeRole.Reviewer)) {
					reviewers.add(attendee);
					if(attendee.getAspects() != null)
						attendee.getAspects().clear();
					else
						attendee.setAspects(new IdArrayList<Aspect>());
				}
			}
			
			IdList<Aspect> aspects = getAspectsModel().getAspects();
			
			Random rand = new Random();
			int aspectsSize = aspects.size();
			int reviewersSize = reviewers.size();
			int reserved = 0;
			int counter = 1; // aspectsSize, aspectsSize-1, ... , 2, 1, [start]
			int freeReviewer;
			for(int i = 0 ; i < aspectsSize ; i++) {
				Aspect aspect = aspects.get(i);
				for(int j = 0 ; j < to ; j++) {
					counter = counter <= 1 ? reviewersSize : --counter;
					
					if(j >= reserved)
						freeReviewer = rand.nextInt(counter - reserved);
					else
						freeReviewer = rand.nextInt(counter);
					
					Attendee r = reviewers.remove(freeReviewer);
					reviewers.add(reviewersSize - 1, r);
					r.getAspects().add(aspect);
				}
				reserved = counter - 1;
			}
			
			/*
				Random rand = new Random();
				int reserved = 0;
				int counter = 1; // counts g, g-1, g-2, ... , 2, 1, g ,...
				int freeReviewer;
				for (int i=0; i<aspects.size();i++) {
					for (int k=0;k<over;k++) {
						counter = counter <= 1 ? reviewers.size() : --counter;
						
						if(k >= reserved)
							freeReviewer = rand.nextInt(counter - reserved);
						else
							freeReviewer = rand.nextInt(counter);
						
						Reviewer r = reviewers.remove(freeReviewer);
						reviewers.add(reviewers.size(), r);
						r.add(aspects.get(i));
					}
					reserved = counter - 1;
				}
			 */
			
			setAttendeeSlideBoxPanel(attendeeList);
			
		}
	}
	
	/**
	 * validate the distributer function
	 * @param select item to select
	 */
	public void validateDistributer() {
		int selected = distributeComboBox.getSelectedIndex();
		selected = Math.max(0, selected);
		
		int count = reviewersModel.getRoot().getChildCount();
		String[] values = new String[count + 1];
		values[0] = getLocalizedString("distribute.to_all");
		for(int i = 0 ; i < count ; i++) {
			Formatter formatter = new Formatter();
			formatter.format(getLocalizedString("distribute.to"), i+1);			
			values[i+1] = formatter.toString();
		}
		
		distributeComboBox.setModel(new DefaultComboBoxModel(values));
		
		if(selected < distributeComboBox.getItemCount())
			distributeComboBox.setSelectedIndex(selected);
	}
	
	@Override
	public void addedPerformed(SlideBoxChangeEvent sbce) {
		Attendee attendee = (Attendee)sbce.getElement();
		if(!attendee.getRole().equals(AttendeeRole.Reviewer))
			return;
			
		TreePath rootPath = new TreePath(reviewersModel.getRoot());
		
		if(attendee.getAspects() == null)
			attendee.setAspects(new IdArrayList<Aspect>());
		
		TreePath addedPath = reviewersModel.addAspectsNode(rootPath, 
				attendee.getAspects(), 
				attendee.getName());
		
		DefaultTreeNode dtn = (DefaultTreeNode)addedPath
				.getLastPathComponent();
		
		dtn.setCustomObject(attendee);
		
		reviewersTree.expandPath(addedPath);
		
		validateDistributer();
	}
	@Override
	public void changedPerformed(SlideBoxChangeEvent sbce) {
		Attendee attendee = (Attendee)sbce.getElement();
		DefaultTreeNode dtnRoot = reviewersModel.getRoot();
		TreePath rootPath = new TreePath(dtnRoot);
		
		int count = dtnRoot.getChildCount();
		
		for(int i = 0 ; i < count ; i++) {
			DefaultTreeNode dtnChild = dtnRoot.getChildAt(i);
			
			if(attendee == null || attendee != dtnChild.getCustomObject())
				continue;
			
			TreePath aPath = rootPath.pathByAddingChild(dtnChild);
			
			if(attendee.getRole().equals(AttendeeRole.Reviewer)) {
				reviewersModel.editAspectsNode(aPath, 
						attendee.getName());
			} else {
				AspectsTreeNode atn = (AspectsTreeNode)dtnChild;
				IdList<Aspect> lst = reviewersModel
						.getAspectsOfAspectsNode(atn);
				attendee.setAspects(lst);
				
				reviewersModel.removeNode(aPath);
			}
			
			validateDistributer();
			return;
		}
		
		addedPerformed(sbce);
	}
	@Override
	public void clearedPerformed(SlideBoxChangeEvent sbce) {
		DefaultTreeNode dtnRoot = reviewersModel.getRoot();
		TreePath rootPath = new TreePath(dtnRoot);
		
		int count = dtnRoot.getChildCount();
		
		for(int i = 0 ; i < count ; i++) {
			DefaultTreeNode dtnChild = dtnRoot.getChildAt(i);
			
			Attendee attendee = (Attendee)dtnChild.getCustomObject();
			
			TreePath aPath = rootPath.pathByAddingChild(dtnChild);
			
			AspectsTreeNode atn = (AspectsTreeNode)dtnChild;
			IdList<Aspect> lst = reviewersModel
						.getAspectsOfAspectsNode(atn);
			attendee.setAspects(lst);
				
			reviewersModel.removeNode(aPath);
		}
		
		reviewersModel
				.createEmptyAspectsRoot(getLocalizedString("reviewers"));
		
		reviewersTree.setSelectionPath(
				new TreePath(reviewersTree.getModel().getRoot()));
		
		validateDistributer();
	}
	@Override
	public void movedPerformed(SlideBoxChangeEvent sbce) {
		// do nothing
	}
	@Override
	public void removedPerformed(SlideBoxChangeEvent sbce) {
		Attendee attendee = (Attendee)sbce.getElement();
		DefaultTreeNode dtnRoot = reviewersModel.getRoot();
		TreePath rootPath = new TreePath(dtnRoot);
		
		int count = dtnRoot.getChildCount();
		
		for(int i = 0 ; i < count ; i++) {
			DefaultTreeNode dtnChild = dtnRoot.getChildAt(i);
			
			if(attendee == null || attendee != dtnChild.getCustomObject())
				continue;
			
			TreePath aPath = rootPath.pathByAddingChild(dtnChild);
			
			AspectsTreeNode atn = (AspectsTreeNode)dtnChild;
			IdList<Aspect> lst = reviewersModel
						.getAspectsOfAspectsNode(atn);
			attendee.setAspects(lst);
				
			reviewersModel.removeNode(aPath);
			
			validateDistributer();
			
			return;
		}
	}
	
	@Override
	public void changedAspect(Aspect aspect) {
		String category = aspect.getCategory();
		
		TreeNode root = reviewersModel.getRoot();
		TreePath rootPath = new TreePath(root);
		int childCount = root.getChildCount();
		
		for(int i = 0 ; i < childCount ; i++) {
			AspectsTreeNode atn = (AspectsTreeNode)root.getChildAt(i);
			TreePath atnPath = rootPath.pathByAddingChild(atn);
			TreePath cPath = getReviewerCategory(atnPath, category, false);
				
			
			if(cPath == null && category != null && !category.equals(""))
				continue;
			
		
			TreeNode tn = root;
			TreePath tnPath = rootPath;
			if(cPath != null) {
					tn = (TreeNode)cPath.getLastPathComponent();
					tnPath = cPath;
			}
			
			int aspectsCount = tn.getChildCount();
			
			for(int j = 0 ; j < aspectsCount ; j++) {
				TreeNode n = tn.getChildAt(j);
				if(!(n instanceof AspectTreeNode))
					continue;
				AspectTreeNode chNode = (AspectTreeNode)n;
				if(chNode.getAspect() == aspect) {
					TreePath aspectPath = tnPath.pathByAddingChild(chNode);
					reviewersModel.editAspectNode(aspectPath, 
							aspect.getDirective(), 
							aspect.getDescription());
				}
			}
		}
	}

	@Override
	public void changedAspect(Aspect aspect, String oldCategory) {
		// TODO better handling
		setAttendeeSlideBoxPanel(attendeeList);
	}
	
	@Override
	public void changedCategory(String oldCategory, String newCategory) {
		TreeNode root = reviewersModel.getRoot();
		TreePath rootPath = new TreePath(root);
		int childCount = root.getChildCount();
			
		for(int i = 0 ; i < childCount ; i++) {
			AspectsTreeNode atn = (AspectsTreeNode)root.getChildAt(i);
			TreePath atnPath = rootPath.pathByAddingChild(atn);
			TreePath cPath = getReviewerCategory(atnPath, 
					oldCategory, 
					false);
			
			if(cPath == null)
				continue;
			
			reviewersModel.editCategoryNode(cPath, newCategory);
			
		}
	}
		@Override
	public void removedAspect(Aspect aspect) {
		// TODO better handling	
		
		int attendeeCount = attendeeList.getListSize();
		for(int i = 0 ; i < attendeeCount ; i++) {
			Attendee attendee = attendeeList.getListElement(i);
			if(attendee.getAspects() == null)
				continue;
			
			attendee.getAspects().remove(aspect);
		}
		
		setAttendeeSlideBoxPanel(attendeeList);
	}
		
	@Override
	public void removedCategory(String category) {
		// TODO better handling
		int attendeeCount = attendeeList.getListSize();
		for(int i = 0 ; i < attendeeCount ; i++) {
			Attendee attendee = attendeeList.getListElement(i);
			
			IdList<Aspect> aspects = attendee.getAspects();
			if(aspects == null)
				continue;
			
			Aspect[] aspectsTR = Aspects.getCategoryAspects(aspects, 
					category);
			
			aspects.removeAll(Arrays.asList(aspectsTR));
		}
		
		setAttendeeSlideBoxPanel(attendeeList);
	}
	
	@Override
	public void clearedAspects() {
		// TODO better handling
		int attendeeCount = attendeeList.getListSize();
		for(int i = 0 ; i < attendeeCount ; i++) {
			Attendee attendee = attendeeList.getListElement(i);
			
			attendee.setAspects(null);
		}
		
		setAttendeeSlideBoxPanel(attendeeList);
	}

	@Override
	public String getHint() {
		return Messages.getString("PearReview.hint.moderator.distribute");
	}

	@Override
	public String getErrorMessage() {
		return null;
	}

	@Override
	public void setNextViewAction(Action action) {
		bottom.setNextViewAction(action);
	}

	@Override
	public void setPreviousViewAction(Action action) {
		bottom.setPreviousViewAction(action);
	}
}
